/*
  Shiki Highlighter singleton with fine-grained bundle, JS regex engine, and lazy lang/theme loading.
  - Avoid importing `shiki` root. Use `shiki/core` and `shiki/engine/javascript`.
  - Cache highlighter across calls.
  - Load only required language and theme on demand.
*/

import { createHighlighterCore } from "shiki/core";
import { createJavaScriptRegexEngine } from "shiki/engine/javascript";

type ThemeMode = "light" | "dark";
type ThemeName = "vitesse-light" | "vitesse-dark";

type HighlighterCompat = {
  getLoadedLanguages?(): string[];
  getLoadedThemes?(): string[];
  loadLanguage?(lang: unknown): Promise<void> | void;
  loadTheme?(theme: unknown): Promise<void> | void;
  codeToHtml(
    code: string,
    options: { lang: string; theme: string },
  ): Promise<string> | string;
  dispose?(): void;
};

let highlighterPromise: Promise<HighlighterCompat> | null = null;

function getThemeName(mode: ThemeMode): ThemeName {
  return mode === "dark" ? "vitesse-dark" : "vitesse-light";
}

async function getHighlighter(): Promise<HighlighterCompat> {
  if (!highlighterPromise) {
    highlighterPromise = createHighlighterCore({
      themes: [],
      langs: [],
      engine: createJavaScriptRegexEngine(),
    }) as unknown as Promise<HighlighterCompat>;
  }
  return highlighterPromise;
}

// Minimal language normalization/alias
function normalizeLang(lang: string): string {
  const l = (lang || "").toLowerCase();
  switch (l) {
    case "js":
      return "javascript";
    case "ts":
      return "typescript";
    case "tsx":
      return "tsx";
    case "jsx":
      return "jsx";
    case "sh":
      return "bash";
    case "yml":
      return "yaml";
    case "md":
      return "markdown";
    case "svg":
      return "xml"; // closest
    default:
      return l;
  }
}

// Fine-grained language loaders. Extend when needed.
const langLoaders: Record<string, () => Promise<unknown>> = {
  typescript: () => import("@shikijs/langs/typescript"),
  javascript: () => import("@shikijs/langs/javascript"),
  tsx: () => import("@shikijs/langs/tsx"),
  jsx: () => import("@shikijs/langs/jsx"),
  json: () => import("@shikijs/langs/json"),
  html: () => import("@shikijs/langs/html"),
  xml: () => import("@shikijs/langs/xml"),
  css: () => import("@shikijs/langs/css"),
  scss: () => import("@shikijs/langs/scss"),
  less: () => import("@shikijs/langs/less"),
  bash: () => import("@shikijs/langs/bash"),
  shell: () => import("@shikijs/langs/shell"),
  python: () => import("@shikijs/langs/python"),
  go: () => import("@shikijs/langs/go"),
  rust: () => import("@shikijs/langs/rust"),
  java: () => import("@shikijs/langs/java"),
  csharp: () => import("@shikijs/langs/csharp"),
  cpp: () => import("@shikijs/langs/cpp"),
  yaml: () => import("@shikijs/langs/yaml"),
  markdown: () => import("@shikijs/langs/markdown"),
  sql: () => import("@shikijs/langs/sql"),
  dockerfile: () => import("@shikijs/langs/dockerfile"),
  ini: () => import("@shikijs/langs/ini"),
  toml: () => import("@shikijs/langs/toml"),
  diff: () => import("@shikijs/langs/diff"),
};

export function isLanguageSupported(lang: string): boolean {
  const norm = normalizeLang(lang);
  return norm in langLoaders;
}

async function ensureLanguageLoaded(lang: string) {
  const norm = normalizeLang(lang);
  const loader = langLoaders[norm];
  if (!loader) return false;
  const highlighter = await getHighlighter();
  const loaded = highlighter.getLoadedLanguages?.() ?? [];
  if (loaded.includes(norm)) return true;
  try {
    const mod = (await loader()) as { default?: unknown } | unknown;
    const langDef = (mod as { default?: unknown })?.default ?? mod;
    (highlighter as { loadLanguage?: (l: unknown) => unknown }).loadLanguage?.(
      langDef,
    );
    return true;
  } catch {
    return false;
  }
}

async function ensureThemeLoaded(mode: ThemeMode) {
  const name = getThemeName(mode);
  const highlighter = await getHighlighter();
  const loaded = highlighter.getLoadedThemes?.() ?? [];
  if (loaded.includes(name)) return name;
  const mod: { default?: unknown } | unknown = await import(
    name === "vitesse-dark"
      ? "@shikijs/themes/vitesse-dark"
      : "@shikijs/themes/vitesse-light"
  );
  const themeDef = (mod as { default?: unknown })?.default ?? mod;
  (highlighter as { loadTheme?: (t: unknown) => unknown }).loadTheme?.(
    themeDef,
  );
  return name;
}

export async function codeToHtmlCached(
  code: string,
  lang: string,
  mode: ThemeMode,
) {
  const ok = await ensureLanguageLoaded(lang);
  if (!ok) return "";
  const theme = await ensureThemeLoaded(mode);
  const highlighter = await getHighlighter();
  const html = await highlighter.codeToHtml(code, {
    lang: normalizeLang(lang),
    theme,
  });
  // Ensure shiki css vars exist by adding a minimal wrapper if missing
  // If theme loader didn't inject, we still return html and container style extraction will fallback
  return html;
}

export async function disposeHighlighter() {
  if (highlighterPromise) {
    try {
      const h = await highlighterPromise;
      (h as { dispose?: () => void }).dispose?.();
    } catch {}
    highlighterPromise = null;
  }
}
